Unity解析OSM数据,并生成简单模型 | 您所在的位置:网站首页 › unity 地图模型 › Unity解析OSM数据,并生成简单模型 |
文章目录
一、介绍XML数据格式二、Unity解析XML数据格式的方法1.C#自带的方法2.Unity读取TextAsset方法
三、OSM数据介绍四、Unity解析OSM数据1.定义node和way的数据结构2.获取XML文件中node和way的属性值并存储
五、使用LineRender对OSM数据进行简单可视化六、根据OSM数据创建道路和建筑的简单模型1.重新定义node和way的数据结构2.创建模型
七、参考
一、介绍XML数据格式
因为OSM数据就是XML数据格式,所以先介绍一下XML数据格式。 XML 指可扩展标记语言(eXtensible Markup Language)。 XML 被设计用来传输和存储数据。XML 文档形成了一种树结构,它从"根部"开始,然后扩展到"枝叶"。XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。 XML 文档必须包含根元素。该元素是所有其他元素的父元素。所有的元素都可以有文本内容和属性(类似 HTML 中)。在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签。XML 文档实例 Tove Jani Reminder Don't forget me this weekend!以上显示了根元素note及它的四个子元素(Element)。 二、Unity解析XML数据格式的方法 1.C#自带的方法 //引入命名空间 using System.Xml; public class Parser : MonoBehaviour { //创建xml文档对象 private XmlDocument doc = new XmlDocument(); private void Start() { //读入xml文件 XmlTextReader reader= new XmlTextReader("Assets/" + mapName); //加载到xml文档对象中 doc.Load(reader); //根据元素名称获取元素list XmlNodeList elemList = doc.GetElementsByTagName("node"); //获取元素的子元素们 foreach (XmlNode node in elemList ) { XmlNodeList children = node.ChildNodes; } //获取元素的某一属性名称 string attrName = elemList[0].Attributes[0].Name; //获取元素的某一属性值 string idStr = elemList[0].Attributes["id"].InnerText; //把string转为int int id = int.Parse(idStr); } } 2.Unity读取TextAsset方法TextAsset是用于导入文本文件的格式。当您将文本文件放入项目文件夹时,它将被转换为TextAsset。支持的文本格式有:.txt、.html、.htm、.xml、.bytes、.json、.csv、.yaml、.fnt。 using System.Xml; [Tooltip("The resource file that contains the OSM map data")] public string resourceFile; [HideInInspector] public Dictionary nodes; void Start() { //读取文件 var txtAsset = Resources.Load(resourceFile); //加载xml内容到xml文件对象 XmlDocument doc = new XmlDocument(); doc.LoadXml(txtAsset.text); //获取单个元素 XmlNode xmlNode = doc.SelectSingleNode("/osm/bounds"); //获取元素list XmlNodeList xmlNodeList = doc.SelectNodes("/osm/node")); } 三、OSM数据介绍OpenStreetMap的元素(数据基元)主要包括三种: 点(Nodes)路(Ways)关系(Relations)这三种元素构成了整个地图画面。其中,Nodes定义了空间中点的位置;Ways定义了线或区域;Relations(可选的)定义了元素间的关系。 根元素: node元素: 可以看到,node元素的属性有:id、lat、lon、user、uid、visible、version、changeset、timestamp。我们实际上需要的只有前三个属性。其他属性的介绍可以看官方介绍。 关于Tag元素: Tags describe the meaning of the particular element to which they are attached. 简单来说就是用一些键值对表示这些元素的地图特征。并没有一个固定的字典来表示这些tag,但是可以参考这里:Map Features page。 way元素: 每个way包含了多个nd元素。way的属性中我们也只需要id。 relation元素: relation元素记录了两个或多个数据元素(node、way和/或其他relation)之间的关系。例如:一种路线关系,它列出了组成主要公路、自行车路线或公共汽车路线的路线。relation元素的含义可以有很多,还是要依据tag来看。 四、Unity解析OSM数据 1.定义node和way的数据结构 using System.Collections.Generic; public struct Node { public int id; public float lat, lon; public Node(int ID, float LAT, float LON) { id = ID; lat = LAT; lon = LON; } } public struct Way { public int id; public List wnodes; public Way(int ID) { id = ID; wnodes = new List(); } } 2.获取XML文件中node和way的属性值并存储 private List nodes = new List(); private List ways = new List(); [SerializeField] private string mapName = "map2.osm"; doc.Load(new XmlTextReader("Assets/" + mapName)); //【存储所有nodes】 XmlNodeList elemList = doc.GetElementsByTagName("node"); for (int i = 0; i XmlNodeList wayNodes = node.ChildNodes; //存储way的id ways.Add(new Way(int.Parse(node.Attributes["id"].InnerText))); //存储way的每个node的id foreach (XmlNode nd in wayNodes) { if (nd.Attributes[0].Name == "ref")//根据元素属性筛选出node元素 { ways[ct].wnodes.Add(int.Parse(nd.Attributes["ref"].InnerText)); Debug.Log(ways[ct].wnodes.Count); } } ct++; }现在nodes和ways两个list已经存储了点和线的id、经纬度信息。 五、使用LineRender对OSM数据进行简单可视化(1)LineRender介绍:设置LineRender组件的Positions数组,确定一条Line上面控制点的位置。设置width曲线控制Line的宽度。比如现在就是长度为1,宽度约0.2的一条Line。
下面这种写法的执行速度很慢,只是体现个思路。 private List wayObjects = new List(); [SerializeField] private float x; [SerializeField] private float y; //在osm文件的bounds元素中已知此区域的经纬度极值 // [SerializeField] private float boundsX = 34; [SerializeField] private float boundsY = -118; for (int i = 0; i foreach (Node nod in nodes)//遍历nodes的list,找到对应node的经纬度 { if (nod.id == ways[i].wnodes[j]) { x = nod.lat; y = nod.lon; } } wayObjects[i].GetComponent().SetPosition(j, new Vector3((x - boundsX) * 100, 0, (y - boundsY) * 100)); } }效果: 创建简单道路和建筑模型要用到mesh编程的知识。简单来说就是给一游戏物体添加MeshFilter组件后,自己去设置这个Mesh的顶点、法线、三角形、uv。最后结果是这样:
建筑除了根据多个点画出底面外,还具备一定的高度。 因为我们要区分哪些点是用于道路的,哪些点是用于建筑的,所以除了之前的“id”、“lan”、“lon”,我们还需要知道一些Tag的值。 (1)优化一下从xml中获取属性的方法,定义一个BaseOsm类: using System; using System.Xml; class BaseOsm { protected T GetAttribute(string attrName, XmlAttributeCollection attributes) { string strValue = attributes[attrName].Value; return (T)Convert.ChangeType(strValue, typeof(T)); } }(2)OSMNode类: using System.Xml; using UnityEngine; class OsmNode : BaseOsm { public ulong ID {get; private set; } public float Latitude { get; private set; } public float Longitude { get; private set; } public float X { get; private set; } public float Y {get; private set; } // implicit conversion between OsmNode and Vector3 //使得node可以像Vector3一样运算 public static implicit operator Vector3 (OsmNode node) { return new Vector3(node.X, 0, node.Y); } public OsmNode(XmlNode node) { ID = GetAttribute("id", node.Attributes); Latitude = GetAttribute("lat", node.Attributes); Longitude = GetAttribute("lon", node.Attributes); //从经纬度转坐标的方法分离出去了 X = (float)MercatorProjection.lonToX(Longitude); Y = (float)MercatorProjection.latToY(Latitude); } }(3)OSMWay类: using System.Collections.Generic; using System.Xml; class OsmWay : BaseOsm { public ulong ID {get; private set; } public bool Visible { get; private set; } public List NodeIDs {get; private set; } public bool IsBoundary {get; private set; }//边界可以在场景中画出,方便判断 public bool IsBuilding {get; private set; } public bool IsRoad {get; private set; } public float Height {get; private set; } public OsmWay(XmlNode node) { NodeIDs = new List(); ID = GetAttribute("id", node.Attributes); Visible = GetAttribute("visible", node.Attributes); //保存way中的nodes的id XmlNodeList nds = node.SelectNodes("nd"); foreach (XmlNode n in nds) { ulong refNo = GetAttribute("ref", n.Attributes); NodeIDs.Add(refNo); } //判断是否是边界 if (NodeIDs.Count > 1) { IsBoundary = NodeIDs[0] == NodeIDs[NodeIDs.Count - 1]; } Height = 10.0f; XmlNodeList tags = node.SelectNodes("tag"); foreach (XmlNode t in tags) { string key = GetAttribute("k", t.Attributes); if (key == "height")//不确定是建筑还是道路高度 { Height = 0.3048f * GetAttribute("v", t.Attributes); } else if (key == "building:levels")//建筑层数,每一层是3m { Height = 3.0f * GetAttribute("v", t.Attributes); } else if (key == "building")//是建筑 { IsBuilding = GetAttribute("v", t.Attributes) == "yes"; Height = 10.0f; } else if (key == "highway")//是公路 { IsRoad = true; } /** would preferably like to use only: ** trunk roads ** primary roads ** secondary roads ** service roads */ } } } 2.创建模型(1)定义基类 using UnityEngine; [RequireComponent(typeof(MapReader))]//该物体应当添加了MapReader组件 abstract class InfrstructureBehaviour : MonoBehaviour { protected MapReader map; void Awake() { map = GetComponent(); } protected Vector3 GetCentre(OsmWay way) { Vector3 total = Vector3.zero; foreach (var id in way.NodeIDs) { total += map.nodes[id]; } return total / way.NodeIDs.Count; } }(2)创建道路 using System.Collections; using System.Collections.Generic; using UnityEngine; class RoadMaker : InfrstructureBehaviour { public Material roadMaterial; IEnumerator Start() { while (!map.IsReady){yield return null;}//等待地图数据解析完毕 foreach (var way in map.ways.FindAll((w) => { return w.IsRoad; })) { //【1】创建一个游戏物体,并将它的位置设置为道路中心 GameObject go = new GameObject(); Vector3 localOrigin = GetCentre(way);//找到这条道路的中心位置 go.transform.position = localOrigin - map.bounds.Centre;//减掉整个地图的中心位置,就是应该在场景中显示的道路中心了 //【2】向游戏物体添加MeshFilter和MeshRenderer组件 MeshFilter mf = go.AddComponent(); MeshRenderer mr = go.AddComponent(); //设置材质 mr.material = roadMaterial; //设置顶点位置、法线、uv、三角形 List vectors = new List(); List normals = new List(); List uvs = new List(); List indices = new List(); for (int i = 1; i normals.Add(-Vector3.up);} //设置三角形的顶点索引 // index values int idx1, idx2,idx3, idx4; idx4 = vectors.Count - 1; idx3 = vectors.Count - 2; idx2 = vectors.Count - 3; idx1 = vectors.Count - 4; // first triangle v1, v3, v2 indices.Add(idx1); indices.Add(idx3); indices.Add(idx2); // second triangle v3, v4, v2 indices.Add(idx3); indices.Add(idx4); indices.Add(idx2); // third triangle v2, v3, v1 indices.Add(idx2); indices.Add(idx3); indices.Add(idx1); // fourth triangle v2, v4, v3 indices.Add(idx2); indices.Add(idx4); indices.Add(idx3); } mf.mesh.vertices = vectors.ToArray(); mf.mesh.normals = normals.ToArray(); mf.mesh.triangles = indices.ToArray(); mf.mesh.uv = uvs.ToArray(); yield return null; } } }(3)创建建筑物 using System.Collections; using System.Collections.Generic; using UnityEngine; class BuildingMaker : InfrstructureBehaviour { public Material building; IEnumerator Start() { while (!map.IsReady){yield return null;} foreach (var way in map.ways.FindAll((w) => { return w.IsBuilding && w.NodeIDs.Count > 1; })) { GameObject go = new GameObject(); Vector3 localOrigin = GetCentre(way); go.transform.position = localOrigin - map.bounds.Centre; MeshFilter mf = go.AddComponent(); MeshRenderer mr = go.AddComponent(); mr.material = building; List vectors = new List(); List normals = new List(); List indices = new List(); for (int i = 1; i normals.Add(-Vector3.forward);} // index values int idx1, idx2,idx3, idx4; idx4 = vectors.Count - 1; idx3 = vectors.Count - 2; idx2 = vectors.Count - 3; idx1 = vectors.Count - 4; // first triangle v1, v3, v2 indices.Add(idx1); indices.Add(idx3); indices.Add(idx2); // second triangle v3, v4, v2 indices.Add(idx3); indices.Add(idx4); indices.Add(idx2); // third triangle v2, v3, v1 indices.Add(idx2); indices.Add(idx3); indices.Add(idx1); // fourth triangle v2, v4, v3 indices.Add(idx2); indices.Add(idx4); indices.Add(idx3); } mf.mesh.vertices = vectors.ToArray(); mf.mesh.normals = normals.ToArray(); mf.mesh.triangles = indices.ToArray(); yield return null; } } } 七、参考 使用LineRender可视化:OSM-2-Unity创建简单模型:osm-unity |
CopyRight 2018-2019 实验室设备网 版权所有 |